//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

namespace Microsoft.Research.eResearch.Execution
{
    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel.Design;
    using System.ComponentModel.Design.Serialization;
    using System.Reflection;
    using System.Text;
    using System.Workflow.ComponentModel.Compiler;
    using System.Workflow.ComponentModel.Serialization;
    using System.Xml;
    using Microsoft.Research.eResearch.Common.Linq;
    using CompModel = System.Workflow.ComponentModel;

    /// <summary>
    /// This class contains the logic to serialize the workflow.
    /// </summary>
    internal class CustomWorkflowSerializer : IDisposable
    {
        /// <summary>
        /// Flag to indicate whether this object is disposed already.
        /// </summary>
        private bool disposed;

        /// <summary>
        /// Instance of the component model activity.
        /// </summary>
        private CompModel.Activity rootActivity;

        /// <summary>
        /// Path of the XOML.
        /// </summary>
        private string xomlPath;

        /// <summary>
        /// Instance of ServiceContainer.
        /// </summary>
        private ServiceContainer serviceContainer;

        /// <summary>
        /// Instance of TypeProvider.
        /// </summary>
        private TypeProvider typeProvider;

        /// <summary>
        /// Instance of DesignerSerializationManager.
        /// </summary>
        private DesignerSerializationManager manager;

        /// <summary>
        /// Initializes a new instance of the CustomWorkflowSerializer class.
        /// </summary>
        /// <param name="rootActivity">Instance of the component model activity.</param>
        /// <param name="xomlPath">Path of the XOML.</param>
        /// <param name="assemblies">Loaded assemblies.</param>
        public CustomWorkflowSerializer(CompModel.Activity rootActivity, string xomlPath, Collection<Assembly> assemblies)
        {
            this.rootActivity = rootActivity;
            this.xomlPath = xomlPath;
            this.serviceContainer = new ServiceContainer();
            this.typeProvider = new TypeProvider(this.serviceContainer);          
            this.serviceContainer.AddService(typeof(ITypeProvider), this.typeProvider);
            this.manager = new DesignerSerializationManager(this.serviceContainer);

            assemblies.ForEach(assembly => this.typeProvider.AddAssembly(assembly));
        }

        /// <summary>
        /// Serialize the workflow.
        /// </summary>
        public void Serialize()
        {
            ThrowIfDisposed();

            ValidationErrorCollection errors = new ValidationErrorCollection();

            using (XmlWriter writer = new XmlTextWriter(xomlPath, Encoding.UTF8))
            {
                try
                {
                    using (manager.CreateSession())
                    {
                        // Defines the WorkflowMarkupSerializationManager which will be used to get 
                        // the WFAcitivty from XOML File.
                        WorkflowMarkupSerializationManager serializationManager = new WorkflowMarkupSerializationManager(manager);

                        // This is used to Save the XOML into the XMLWriter.
                        WorkflowMarkupSerializer serializer = new WorkflowMarkupSerializer();
                        serializer.Serialize(serializationManager, writer, rootActivity);

                        // Check for errors.
                        CheckSerializationErrors(errors);
                    }
                }
                catch (Exception exception)
                {
                    errors.Add(new ValidationError(exception.Message, 0x15b));
                }
            }

            // If there are any errors while serializing, throw WorkflowValidationFailedException 
            // with the ValidationErrorCollection as the argument.
            if (errors.HasErrors)
            {
                throw new WorkflowValidationFailedException("Serialization failed", errors);
            }
        }

        /// <summary>
        /// Disposes all the IDisposable members and marks itself as disposed.
        /// </summary>
        public void Dispose()
        {
            ThrowIfDisposed();
            typeProvider.Dispose();
            serviceContainer.Dispose();
            typeProvider = null;
            serviceContainer = null;
            disposed = true;
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Checks the serialization errors.
        /// </summary>
        /// <param name="errors">Collection to be updated if errors found.</param>
        private void CheckSerializationErrors(ValidationErrorCollection errors)
        {
            // If there were errors while Serializing the XOML file, 
            // then all the error will be put into a ValidationErrorCollection.
            foreach (object serializeError in manager.Errors)
            {
                WorkflowMarkupSerializationException serializationException = serializeError as WorkflowMarkupSerializationException;

                if (serializationException != null)
                {
                    errors.Add(new ValidationError(serializationException.Message, 0x15b));
                }
                else
                {
                    errors.Add(new ValidationError(serializeError.ToString(), 0x15b));
                }
            }
        }

        /// <summary>
        /// Throws the ObjectDisposedException if operation is performed on the disposed object. 
        /// </summary>
        private void ThrowIfDisposed()
        {
            if (disposed)
            {
                throw new ObjectDisposedException("CustomWorkflowSerializer");
            }
        }
    }
}
